/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package genesisblock

import (
	"errors"
	"fmt"
	"strconv"

	"hundsun.com/hsl/hschain/protos/common"

	"github.com/golang/protobuf/proto"
	clicommon "hundsun.com/hsl/hschain/cmd/cli/common"
	"hundsun.com/hsl/hschain/protos/config"
	"hundsun.com/hsl/hschain/protos/ledger"
)

//#####################DPOS共识相关#####################

// DPOSGenesisBlockYamlConfig  采用DPOS共识的创世块yaml文件对应的结构体
type DPOSGenesisBlockYamlConfig struct {
	BaseGenesisBlockYamlConfig `yaml:",inline"`
	Consensus                  *DPOSConsensusConfig
}

// ConvertToProtoMessage 在基类的基础上，解析了共识字段
func (d *DPOSGenesisBlockYamlConfig) ConvertToProtoMessage() error {
	err := d.BaseGenesisBlockYamlConfig.ConvertToProtoMessage()
	if err != nil {
		return err
	}

	consensusConfigBytes, err := d.Consensus.Payload.ConvertToProtoMessageBytes()
	if err != nil {
		return err
	}

	d.genesisTxPayload.Consensus = &config.ConsensusConfig{
		Type:    config.DposConsensusType,
		Payload: consensusConfigBytes,
	}

	err = d.genesisTxPayload.Validate()
	if err != nil {
		return err
	}
	return nil
}

// InitFromProtoMessage 在基类的基础上解析了consensus字段
func (d *DPOSGenesisBlockYamlConfig) InitFromProtoMessage(genesisBlock *ledger.Block) error {
	err := d.BaseGenesisBlockYamlConfig.InitFromProtoMessage(genesisBlock)
	if err != nil {
		return err
	}
	d.Consensus = &DPOSConsensusConfig{}
	d.Consensus.Type = config.DposConsensusType
	d.Consensus.Payload = &DposConsensusConfigPayload{}
	d.Consensus.Payload.InitFromProtoMessageBytes(d.genesisTxPayload.Consensus.Payload)
	return nil
}

// DPOSConsensusConfig DPOS共识对应的consensus配置对象
type DPOSConsensusConfig struct {
	BaseConsensusConfig `yaml:",inline"`
	Payload             *DposConsensusConfigPayload `yaml:"payload"`
}

var errFmtDPOSConsensusChildElementZero = "child element '%s' of consensus.payload[type=dpos] cannot be zero"

// DposConsensusConfigPayload DPOS共识相关配置
type DposConsensusConfigPayload struct {
	MinerCount           string                                 `yaml:"minerCount"`
	DefaultMiners        []*clicommon.NetworkChainAccountConfig `yaml:"defaultMiners"`
	RoundInterval        string                                 `yaml:"roundInterval"`
	TimeSliceLength      string                                 `yaml:"timeSliceLength"`
	MaxBlocksInTimeSlice string                                 `yaml:"maxBlocksInTimeSlice"`
}

//ConvertToProtoMessageBytes convert to proto message bytes
func (d *DposConsensusConfigPayload) ConvertToProtoMessageBytes() ([]byte, error) {
	MinerCount, err := stringToUint32("consensus.payload.minerCount[type=dpos]", d.MinerCount)
	if err != nil {
		return nil, err
	}
	RoundInterval, err := stringToUint32("consensus.payload.roundInterval[type=dpos]", d.RoundInterval)
	if err != nil {
		return nil, err
	}

	TimeSliceLength, err := stringToUint32("consensus.payload.timeSliceLength[type=dpos]", d.TimeSliceLength)
	if err != nil {
		return nil, err
	}
	if TimeSliceLength == 0 {
		return nil, fmt.Errorf(errFmtDPOSConsensusChildElementZero, "timeSliceLength")
	}

	MaxBlocksInTimeSlice, err := stringToUint32("consensus.payload.maxBlocksInTimeSlice[type=dpos]", d.MaxBlocksInTimeSlice)
	if err != nil {
		return nil, err
	}
	if MaxBlocksInTimeSlice == 0 {
		return nil, fmt.Errorf(errFmtDPOSConsensusChildElementZero, "maxBlocksInTimeSlice")
	}

	if len(d.DefaultMiners) == 0 {
		return nil, errors.New("consensus.payload.defaultMiners[type=dpos] not found or is empty")
	}
	pbPayload := &config.DposConsensusConfigPayload{
		MinerCount:           MinerCount,
		DefaultMiners:        make([]*common.Miner, len(d.DefaultMiners)),
		RoundInterval:        RoundInterval,
		TimeSliceLength:      TimeSliceLength,
		MaxBlocksInTimeSlice: MaxBlocksInTimeSlice,
	}
	for i, m := range d.DefaultMiners {
		pbPayload.DefaultMiners[i] = &common.Miner{
			Address: m.Address,
			PeerID:  m.PeerID,
		}
	}
	err = pbPayload.Validate()
	if err != nil {
		return nil, err
	}
	pbPayloadBytes, err := proto.Marshal(pbPayload)
	if err != nil {
		return nil, err
	}
	return pbPayloadBytes, nil
}

//InitFromProtoMessageBytes init from proto message bytes
func (d *DposConsensusConfigPayload) InitFromProtoMessageBytes(bytes []byte) error {
	pbConsensusConfigPayload := config.DposConsensusConfigPayload{}
	err := proto.Unmarshal(bytes, &pbConsensusConfigPayload)
	if err != nil {
		return err
	}
	minerConfigs := make([]*clicommon.NetworkChainAccountConfig, len(pbConsensusConfigPayload.DefaultMiners))
	for i, m := range pbConsensusConfigPayload.DefaultMiners {
		minerConfigs[i] = &clicommon.NetworkChainAccountConfig{
			Address: m.Address,
			PeerID:  m.PeerID,
		}
	}

	d.MinerCount = strconv.FormatUint(uint64(pbConsensusConfigPayload.MinerCount), 10)
	d.DefaultMiners = minerConfigs
	d.RoundInterval = strconv.FormatUint(uint64(pbConsensusConfigPayload.RoundInterval), 10)
	d.TimeSliceLength = strconv.FormatUint(uint64(pbConsensusConfigPayload.TimeSliceLength), 10)
	d.MaxBlocksInTimeSlice = strconv.FormatUint(uint64(pbConsensusConfigPayload.MaxBlocksInTimeSlice), 10)

	return nil
}
